package net.sudot.chess.business.service;

import net.sudot.chess.business.dao.CustomerDao;
import net.sudot.chess.business.dto.CustomerDetailDTO;
import net.sudot.chess.business.model.Customer;
import net.sudot.chess.business.model.CustomerRank;
import net.sudot.chess.business.model.DepositRecord;
import net.sudot.chess.business.model.InviteRecord;
import net.sudot.chess.business.model.PointRecord;
import net.sudot.chess.business.model.SocialUser;
import net.sudot.chess.business.model.User;
import net.sudot.chess.constant.LockConstants;
import net.sudot.chess.constant.PermissionConstants;
import net.sudot.chess.constant.RoleConstants;
import net.sudot.chess.exception.BusinessException;
import net.sudot.commons.lock.Lock;
import net.sudot.commons.security.AuthenticationProvider;
import net.sudot.commons.security.event.UserRegisteredEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.persistence.LockModeType;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

/**
 * 客户信息
 *
 * @author tangjialin on 2018-03-03.
 */
@Service
public class CustomerService extends BaseService<Customer> implements AuthenticationProvider {

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;
    @Resource
    private CustomerDao customerDao;
    @Resource
    private CustomerRankService customerRankService;
    @Resource
    private DepositRecordService depositRecordService;
    @Resource
    private PointRecordService pointRecordService;
    @Resource
    private InviteRecordService inviteRecordService;

    @Override
    public User<Customer> getUser(Object principal) {
        Customer customer = null;
        if (principal instanceof Customer) {
            customer = (Customer) principal;
        } else if (principal instanceof SocialUser) {
            customer = find(((SocialUser) principal).getUserId());
        } else if (principal instanceof String) {
            customer = findByMobile((String) principal);
        }
        return customer;
    }

    @Override
    public Set<String> getPermissions(Object principal) {
        return getPermissions((Customer) principal);
    }

    @Override
    public Set<String> getRoles(Object principal) {
        return getRoles((Customer) principal);
    }

    public Set<String> getPermissions(Customer customer) {
        Set<String> permissions = new HashSet<>();
        if (customer.isGameMaster()) {
            permissions.add(PermissionConstants.GAME_ROOM_MANAGE);
        }
        return permissions;
    }

    public Set<String> getRoles(Customer customer) {
        Set<String> roles = new HashSet<>();
        if (customer.isGameMaster()) {
            roles.add(RoleConstants.GAME_MASTER);
        }
        return roles;
    }

    /**
     * 获取客户信息
     *
     * @param mobile 客户手机号码
     * @return 返回客户信息
     */
    public Customer findByMobile(String mobile) {
        return customerDao.findByMobile(mobile);
    }

    /**
     * 获取客户信息
     *
     * @param username 客户登录名
     * @return 返回客户信息
     */
    public Customer findByUsername(String username) {
        return customerDao.findByUsername(username);
    }

    /**
     * 获取房间玩家信息
     *
     * @param roomId 房间主键
     * @return 返回房间玩家信息
     */
    public List<CustomerDetailDTO> findListDetailByRoom(Long roomId) {
        return customerDao.findListDetailByRoom(roomId);
    }

    /**
     * 获取房间玩家信息
     *
     * @param roomId 房间主键
     * @return 返回房间玩家信息
     */
    public List<Customer> findListByRoom(Long roomId) {
        return customerDao.findListByRoom(roomId);
    }

    /**
     * 获取房间管理员
     *
     * @param roomId 房间主键
     * @return 返回该房间管理员
     */
    public Customer findGameMasterByRoom(Long roomId) {
        return customerDao.findGameMasterByRoom(roomId);
    }

    /**
     * 获取时间段内服务次数最少的GameMaster信息
     *
     * @param start 时间段起始时间(大于等于[>=]start)
     * @param end   时间段结束时间(小于[<]end)
     * @return 返回GameMaster信息
     */
    public Customer findMinCountByDate(Instant start, Instant end) {
        return customerDao.findMinCountByDate(start, end);
    }

    /**
     * 客户注册
     *
     * @param customer 注册的客户信息
     * @param inviter  邀请者
     * @return 返回注册后的客户信息
     */
    @Transactional(rollbackFor = Exception.class)
    public Customer register(Customer customer, Long inviter) {
        String mobile = customer.getMobile();
        Customer existCustomer = mobile == null || mobile.isEmpty() ? null : findByMobile(mobile);
        if (existCustomer != null) { throw new BusinessException("此手机已被注册"); }
        String username = customer.getUsername();
        existCustomer = username == null || username.isEmpty() ? null : findByUsername(username);
        if (existCustomer != null) { throw new BusinessException("此用户名已被注册"); }
        CustomerRank rank = customerRankService.findDefault();
        if (rank != null) {
            customer.setRank(rank.getId());
        }
        customer.setGameMaster(false);
        customer.setEnabled(true);
        customer.setLocked(false);
        customer = save(customer);
        if (inviter != null) {
            // 建立邀请关系
            inviteRecordService.save(new InviteRecord().setInviter(inviter).setInvitee(customer.getId()));
        }
        applicationEventPublisher.publishEvent(new UserRegisteredEvent(this, customer));
        return customer;
    }

    /**
     * 增加余额
     *
     * @param customer 客户
     * @param money    金额值
     * @param type     类型
     * @param memo     备注
     */
    @Lock(name = LockConstants.CUSTOMER_BALANCE, value = "#customer.id")
    @Transactional(rollbackFor = Exception.class)
    public void addBalance(Customer customer, BigDecimal money, DepositRecord.Type type, String memo) {
        if (money == null) { throw new BusinessException("金额不能为空"); }
        if (money.compareTo(BigDecimal.ZERO) == 0) {
            return;
        }

        if (!LockModeType.PESSIMISTIC_WRITE.equals(customerDao.getLockMode(customer))) {
            customerDao.flush();
            customerDao.refresh(customer, LockModeType.PESSIMISTIC_WRITE);
        }

        BigDecimal decimal = Optional.ofNullable(customer.getBalance()).orElse(BigDecimal.ZERO).add(money);
        if (decimal.compareTo(BigDecimal.ZERO) < 0) { throw new BusinessException("余额不足"); }

        customer.setBalance(decimal);
        customerDao.flush();

        DepositRecord depositRecord = new DepositRecord();
        depositRecord.setType(type);
        depositRecord.setMoney(money);
        depositRecord.setBalance(decimal);
        depositRecord.setMemo(memo == null ? type.memo() : memo);
        depositRecord.setUserId(customer.getId());
        depositRecordService.save(depositRecord);
    }

    /**
     * 增加积分
     *
     * @param customer 客户
     * @param point    积分数
     * @param type     类型
     * @param memo     备注
     */
    @Lock(name = LockConstants.CUSTOMER_POINT, value = "#customer.id")
    @Transactional(rollbackFor = Exception.class)
    public void addPoint(Customer customer, long point, PointRecord.Type type, String memo) {
        if (point == 0) {
            return;
        }

        customer = customerDao.find(customer.getId());
        if (!LockModeType.PESSIMISTIC_WRITE.equals(customerDao.getLockMode(customer))) {
            customerDao.flush();
            customerDao.refresh(customer, LockModeType.PESSIMISTIC_WRITE);
        }

        long finalPoint = Optional.ofNullable(customer.getPoint()).orElse(0L) + point;
        if (finalPoint < 0) { throw new BusinessException("积分不足"); }
        customer.setPoint(finalPoint);
        customerDao.flush();

        PointRecord pointRecord = new PointRecord();
        pointRecord.setType(type);
        pointRecord.setPoint(point);
        pointRecord.setBalance(finalPoint);
        pointRecord.setMemo(memo == null ? type.memo() : memo);
        pointRecord.setUserId(customer.getId());
        pointRecordService.save(pointRecord);
    }
}
